#include "libclangmm/clangmm.hpp"
#include <string>
#include <memory>
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
#include <unordered_set>
#include <set>
#include <unordered_map>

using namespace std;

// class Project;
// class FieldDecl;
// class DeclType;

bool debug = true;
bool debugFunc = true;

class Project {
public:
    std::string m_projectName;
    std::unordered_map<std::string, std::vector<std::pair<std::string, std::string>>> m_funcInArgs;
    std::unordered_map<std::string, std::pair<std::string, std::string>> m_funcReturnTypeAndName;
};

Project project;
std::vector<std::string> declarings;

/*
    MOCKER(func)
        .expects(atMost(3)) // 指定所有测试用例能调用的次数
        .with(eq(3))
        .will(returnValue(2))
        .then(returnValue(3))
        .then(invoke(mock_func));
*/

bool getIfStmt(const clangmm::Cursor &funcCursor, clangmm::Cursor &compoundStmtCursor)
{
    std::vector<clangmm::Cursor> child = funcCursor.get_children();
    auto it = find_if(child.cbegin(), child.cend(), [](clangmm::Cursor cursor){
        return cursor.get_kind() == clangmm::Cursor::Kind::IfStmt;
    });
    compoundStmtCursor = *it;
    return it != child.cend();
}

std::string getIndent()
{
    return "    ";
}

void procFuncBody(const clangmm::Cursor &compoundStmtCursor)
{
    debugFunc && cout << endl << "------------------ procFuncBody()" << endl;
    queue<clangmm::Cursor> q;
    q.emplace(compoundStmtCursor);
    static int index = 0;
    while(!q.empty()) {
        clangmm::Cursor curFront = q.front();
        q.pop();
        
        if(curFront.get_kind() == clangmm::Cursor::Kind::CallExpr) {

            clangmm::SourceRange sourceRange = curFront.get_source_range();
            clangmm::SourceLocation sourceStart = sourceRange.get_start();
            clangmm::SourceLocation sourceEnd = sourceRange.get_end();
            cout << "callExpr: " << curFront.get_definition() << endl;
            cout << "line: " << sourceStart.get_offset().line << endl;
            cout << "index: " << sourceStart.get_offset().index << " - " << sourceEnd.get_offset().index << endl;
            // debug && cout << sourceRange << endl;
            // std::string returnType = curFront.get_type_description();
            // std::string returnValueName = "retVal_" + to_string(index++);
            // std::string returnValueDefination = returnType + " " + returnValueName + ";";
            // debugFunc && cout << returnValueDefination << endl;
            // std::string funcName = curFront.get_spelling();
            // char mockTemplate[] = "MOCKER(%s).defaults().will(returnValue(%s);";
            // char mockCode[100];
            // sprintf(mockCode, mockTemplate, funcName.c_str(), returnValueName.c_str());
            // std::string mocker(mockCode);
            // debugFunc && cout << mocker << endl;
        }

        for(auto curTmp : curFront.get_children()) {
            q.emplace(curTmp);
        }
    }
}

bool getCompoundStmt(const clangmm::Cursor &funcCursor, clangmm::Cursor &compoundStmtCursor)
{
    std::vector<clangmm::Cursor> child = funcCursor.get_children();
    auto it = find_if(child.cbegin(), child.cend(), [](clangmm::Cursor cursor){
        return cursor.get_kind() == clangmm::Cursor::Kind::CompoundStmt;
    });
    compoundStmtCursor = *it;
    return it != child.cend();
}

void procFuncDecl(const clangmm::Cursor &funcDecl)
{
    clangmm::Cursor compoundStmtCursor;
    if(getCompoundStmt(funcDecl, compoundStmtCursor)) {
        procFuncBody(compoundStmtCursor);
    }

    std::string returnType = clangmm::Cursor::Type(clang_getResultType(funcDecl.get_type().cx_type)).get_spelling();
    std::string funcName = funcDecl.get_token_spelling();
    std::string funcDeclaring = returnType + " " + funcDecl.get_display_name() + ";";
    if(project.m_funcInArgs.cend() != project.m_funcInArgs.find(funcDeclaring)) {
        return;
    }
    debugFunc && cout << "funcDecl: " << funcDeclaring << endl;
    // debug && cout << funcDecl << endl;
    // debug && cout << funcDecl.get_semantic_parent().get_kind_spelling() << endl;
    // debug && cout << funcDecl.get_source_location() << endl;
    // debug && cout << funcDecl.get_spelling() << endl;
    // debug && cout << funcDecl.get_type_description() << endl;
    // debug && cout << funcDecl.get_definition() << endl;
    project.m_funcInArgs[funcDeclaring] = {};
    project.m_funcReturnTypeAndName[funcDeclaring] = std::make_pair(returnType, funcName);
    auto args = funcDecl.get_arguments();
    // debugFunc && cout << "arguments:" << endl;
    for(auto arg : args) {
        auto type = arg.get_type().get_spelling();
        auto name = arg.get_spelling();
        project.m_funcInArgs[funcDeclaring].emplace_back(std::make_pair(type, name));
        debugFunc && cout << getIndent() << type + " " + name + ";"<< endl;
    }
}

int main()
{
    // clang++ -fsyntax-only -Xclang -ast-dump moc_file.cc
    vector<clangmm::Cursor> funcDeclVec;
    // vector<clangmm::Cursor> compoundStmtVec;
    std::string filepath = "moc_file.cc";
    project.m_projectName = filepath;

    clangmm::TranslationUnit tu(std::make_shared<clangmm::Index>(0, 0), filepath, {});
    clangmm::Cursor cursor = tu.get_cursor();

    queue<clangmm::Cursor> q; // AST并不是严格的树形结构，其中也会有回路，所以需要找替代方案，加个标记
    q.emplace(cursor);
    while(!q.empty()) {
        clangmm::Cursor curFront = q.front();
        q.pop();
        // debug && cout << curFront.get_token_spelling() << endl;
        // debug && cout << "Path: " << curFront.get_source_location().get_path() << endl;

        if(curFront.get_source_location().get_path() == filepath) {
            // debug && cout << curFront.get_kind_spelling() << endl;
            if(curFront.get_kind() == clangmm::Cursor::Kind::FunctionDecl) {
                funcDeclVec.emplace_back(curFront);
            }
        }

        for(const auto &curTmp : curFront.get_children()) {
            q.emplace(curTmp);
        }
    }

    for(const auto &funcDecl : funcDeclVec) {
        procFuncDecl(funcDecl);
    }

    return 0;
}